package datatypes;


/**
 * 
 * @author PSeiferth
 * 
 */
public class OrientedLine2D {

	private Vertex startPoint, endPoint;

	/**
	 * normed direction of the oriented Line defined by endPoint-startPoint
	 */
	private Vertex normedDirection;

	// slope of the oriented Line. (The m in y=mx+n)
	private Double slope;

	// y-intercept of the oriented Line. (The n in y=mx+n)
	private Double yIntercept;

	public OrientedLine2D(Vertex startPoint, Vertex endPoint) {
		this.startPoint = startPoint;
		this.endPoint = endPoint;
		this.updateNormedDirection();
		this.updateSlope();
		this.updateYIntercept();
	}

	private void updateSlope() {
		this.slope = (endPoint.getY() - startPoint.getY())
				/ (endPoint.getX() - startPoint.getX());
	}

	/**
	 * Update the y-Intercept member after one of the points has changed. Note
	 * to update the slope after one the points have changed before calling this
	 * function.
	 */
	private void updateYIntercept() {
		this.yIntercept = startPoint.getY() - startPoint.getX() * this.slope;
	}

	public void setEndPoint(Vertex endPoint) {
		this.endPoint = endPoint;
		this.updateNormedDirection();
		this.updateSlope();
	}

	public Vertex getEndPoint() {
		return endPoint;
	}

	public void setStartPoint(Vertex startPoint) {
		this.startPoint = startPoint;
		this.updateNormedDirection();
		this.updateSlope();
	}

	public Vertex getStartPoint() {
		return startPoint;
	}

	public Vertex getNormedDirection() {
		if (normedDirection == null) {
			this.updateNormedDirection();
		}
		return normedDirection;
	}

	private void updateNormedDirection() {
		this.normedDirection = new Vertex(endPoint.getX() - startPoint.getX(),
				endPoint.getY() - startPoint.getY());

		// norm = sqrt(x^2 + y^2)
		double norm = Math.sqrt(normedDirection.getX() * normedDirection.getX()
				+ normedDirection.getY() * normedDirection.getY());

		normedDirection.setX(normedDirection.getX() / norm);
		normedDirection.setY(normedDirection.getY() / norm);
	}

	/**
	 * Returns true if and only if this OrientedLine2D contains the Point2D
	 * point.
	 * 
	 * @param point
	 * @return
	 */
	public boolean containsPoint(Vertex point) {
		if (this.isLeftOf(point) == 0) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Returns a value >0 if and only if {@link Vertex} point is left of this
	 * {@link OrientedLine2D}. If the point is contained in this line it returns
	 * 0. Otherwise a value <0 which implies that the point is right of this
	 * line.
	 * 
	 * @param point
	 * @return <0 iff point is left of this line, =0 if point is on this line,
	 *         >0 iff point is right of this line.
	 */
	public Double isLeftOf(Vertex point) {
		return startPoint.getX() * endPoint.getY() + point.getX()
				* startPoint.getY() + endPoint.getX() * point.getY()
				- startPoint.getX() * point.getY() - endPoint.getX()
				* startPoint.getY() - point.getX() * endPoint.getY();
	}

	/**
	 * Returns the slope of this {@link OrientedLine2D}. The slope is the m part
	 * of y=mx+n.
	 * 
	 * @return {@link Double} slope
	 */
	public Double getSlope() {
		return this.slope;
	}

	/**
	 * Returns the yIntercept of this {@link OrientedLine2D}. The yIntercep is
	 * the n part of y=mx+n.
	 * 
	 * @return
	 */
	public Double getYIntercept() {
		return this.yIntercept;
	}

	/**
	 * Calculates the angle between this {@link OrientedLine2D} and another one.
	 * @param otherLine
	 * 		The line to determine the angle with.
	 * @return
	 * 		The angle between the two lines.
	 */		
	public double angleWith(OrientedLine2D otherLine) {
		double scalarProduct = this.getNormedDirection().getX()
				* otherLine.getNormedDirection().getX()
				+ this.getNormedDirection().getY()
				* otherLine.getNormedDirection().getY();
		if (scalarProduct < -1.0) {
			// System.err.println("Alarm! scalarProduct = " + scalarProduct);
			scalarProduct = -1.0;
		}
		if (scalarProduct > 1.0) {
			// System.err.println("Alarm! scalarProduct = " + scalarProduct);
			scalarProduct = 1.0;
		}
		return Math.acos(scalarProduct);
	}

	/**
	 * Creates a new OrientedLine2D which is rotated around the startPoint of
	 * this line until it hits the {@link Vertex} point.
	 * 
	 * @param point
	 *            * At this point the rotation will be stopped. This point is
	 *            part of the returned {@link OrientedLine2D}.
	 * @return new rotated {@link OrientedLine2D}
	 */
	public OrientedLine2D rotateTo(Vertex point) {
		return new OrientedLine2D(this.endPoint, point);
	}
}
